package org.bookie.model.services.impl;

import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.bookie.model.Bet;
import org.bookie.model.Category;
import org.bookie.model.Event;
import org.bookie.model.Outcome;
import org.bookie.model.Period;
import org.bookie.model.Wager;
import org.bookie.model.services.BetDAO;
import org.bookie.model.services.EventDAO;
import org.hibernate.Criteria;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

/**
 * Hibernate implementation of {@link EventDAO}
 * 
 * @author zubairov
 * 
 */
public class HibernateEventDAO extends GenericHibernateDAO<Event, Long>
		implements EventDAO {

	private static final String PROPERTY_STATE = "state";
	private static final String PROPERTY_SUCCESSFUL_OUTCOME = "successfulOutcome";
	private static final String PROPERTY_END_DATE = "endDate";
	private static final String PROPERTY_ID = "id";
	private static final String PROPERTY_CATEGORY = "category";
	
	private BetDAO betDAO;

	
	@SuppressWarnings("unchecked")
	public List<Event> find(Period period, Category category) {
		Criteria criteria = getSession().createCriteria(Event.class);
		addPeriodCriteria(PROPERTY_END_DATE, criteria, period);
		criteria.addOrder(Order.desc(PROPERTY_END_DATE));
		if (null != category) {
			criteria.createCriteria("category").add(
					Restrictions.eq(PROPERTY_ID, category.getId()));
		}
		return criteria.list();
	}

	private Criteria addActivenessCriteria(Criteria criteria) {
		GregorianCalendar calendar = new GregorianCalendar();
		criteria.add(Restrictions.eq(PROPERTY_STATE, Event.State.ACTIVE));
		criteria.add(Restrictions.gt(PROPERTY_END_DATE, calendar.getTime()));
		return criteria;
	}
	
	private Criteria addStrongActivenessCriteria(Criteria criteria) {
		addActivenessCriteria(criteria);
		criteria.createCriteria("bets")
					.add(Restrictions.isNull(PROPERTY_SUCCESSFUL_OUTCOME));
		return criteria;
	}

	@SuppressWarnings("unchecked")
	public List<Event> findByCategory(Long id) {
		Criteria criteria = getSession().createCriteria(Event.class);
		addActivenessCriteria(criteria)
			.createCriteria(PROPERTY_CATEGORY).add(Restrictions.eq(PROPERTY_ID, id));
		criteria.addOrder(Order.asc(PROPERTY_END_DATE));
		List<Event> list = criteria.list();
		List<Event> result = new ArrayList<Event>();
		for(Event event:list){
			Object found = CollectionUtils.find(event.getBets(), new Predicate(){
				public boolean evaluate(Object input) {
					Bet bet = (Bet)input;
					return bet.getSuccessfulOutcome()!=null;
				}
				
			});
			if(found==null)
				result.add(event);
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	public List<Event> findByFilter(String filter) {
		GregorianCalendar calendar = new GregorianCalendar();
		Criteria criteria = getSession().createCriteria(Event.class).add(
				Restrictions.like("label", "%" + filter + "%")).add(
				Restrictions.gt(PROPERTY_END_DATE, calendar.getTime()))
				.addOrder(Order.desc(PROPERTY_END_DATE));
		return criteria.list();
	}

	public Event findById(Long id) {
		return (Event) getSession().createCriteria(Event.class).add(
				Restrictions.eq(PROPERTY_ID, id)).uniqueResult();
	}

	@SuppressWarnings("unchecked")
	public List<Bet> findExpiring(int max) {
		Criteria criteria = getSession().createCriteria(Event.class);
		criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
		List<Bet> result = new ArrayList<Bet>();
		List<Event> list = addStrongActivenessCriteria(criteria)
							.addOrder(Order.asc(PROPERTY_END_DATE))
								.setMaxResults(max*10).list();
		for (Event event : list) {
			if(result.size()==max)break;
			result.add(event.getMainBet());
		}
		return result;
	}

	public void cancel(Long id) {
		Transaction tx = null;
		try {
			tx = getSession().beginTransaction();
			Event event = findById(id);
			for (Bet bet : event.getBets()) {
				if(bet.getSuccessfulOutcome()==null){
					Outcome cancel = new Outcome("Canceled", new Double(1));
					bet.addOutcome(cancel);
					bet.setSuccessfulOutcome(cancel);
				}
			}
			List<Wager> wagers = findWagers(id);
			for (Wager wager : wagers) {
				Bet bet = wager.getOutcome().getBet();
				Outcome cancel = findCancelOutcome(bet);
				if(cancel!=null){
					wager.setOdds(new Double(1));
					wager.setOutcome(cancel);
				}
			}
			for (Bet bet : event.getBets()) {
				Outcome cancel = findCancelOutcome(bet);
				Outcome success = bet.getSuccessfulOutcome();
				if(cancel!=null && cancel.equals(success)){
					betDAO.finalise(cancel);
				}
			}
		}catch (RuntimeException e) {
			tx.rollback();
			throw e;
		}
		
	}
	
	
	@SuppressWarnings("unchecked")
	private List<Wager> findWagers(Long id){
		return getSession().createCriteria(Wager.class)
					.createCriteria("outcome")
						.createCriteria("bet")
							.add(Restrictions.eq("event.id", id))
								.list();
		
	}
	
	private Outcome findCancelOutcome(Bet bet){
		Object obj =CollectionUtils.find(bet.getOutcomes(), new Predicate(){
			public boolean evaluate(Object input) {
				Outcome outcome = (Outcome) input;
				return new Double(1).equals(outcome.getOdds());
			}});
		return (Outcome) obj;
	}

	public void setBetDAO(BetDAO betDAO) {
		this.betDAO = betDAO;
	}

}
